/**
 * 
 */
package org.bouncycastle.crypto.engines;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.params.KeyParameter;

/**
 * @author Isaac
 * @see an implementation of the MISTY1, from RFC2994.
 */
public class MISTY1Engine implements BlockCipher {
	private static final int BLOCK_SIZE = 8;

	private byte[] _workingKey = null;
	private boolean _encrypting = false;

	private int[] _EK = new int[32];

	/**
	 * 
	 */
	public MISTY1Engine() {
	}

	public String getAlgorithmName() {
		return "MISTY1";
	}

	public int getBlockSize() {
		return BLOCK_SIZE;
	}

	public void init(boolean forEncryption, CipherParameters params)
			throws IllegalArgumentException {
		if (params instanceof KeyParameter) {
			if (((KeyParameter) params).getKey().length != 16) {
				throw new IllegalArgumentException(
						"illegal MISTY1 key size - should be 16 bytes");
			}

			_encrypting = forEncryption;
			_workingKey = ((KeyParameter) params).getKey();
			setKey(_workingKey);
			return;
		}

		throw new IllegalArgumentException(
				"invalid parameter passed to MISTY1 init - "
						+ params.getClass().getName());
	}

	public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
			throws DataLengthException, IllegalStateException {
		if (_workingKey == null) {
			throw new IllegalStateException(getAlgorithmName()
					+ " not initialised");
		}

		int blockSize = getBlockSize();
		if ((inOff + blockSize) > in.length) {
			throw new DataLengthException("Input buffer too short");
		}

		if ((outOff + blockSize) > out.length) {
			throw new DataLengthException("Output buffer too short");
		}

		if (_encrypting) {
			return encryptBlock(in, inOff, out, outOff);
		} else {
			return decryptBlock(in, inOff, out, outOff);
		}
	}

	public void reset() {
	}

	private void setKey(byte[] key) {
		for (int i = 0; i < 8; ++i) {
			_EK[i] = (((key[i * 2] & 0xff) << 8) | (key[i * 2 + 1] & 0xff)) & 0xffff;
		}
		for (int i = 0; i < 8; ++i) {
			_EK[i + 8] = FI(_EK[i], _EK[(i + 1) % 8]);
			_EK[i + 16] = _EK[i + 8] & 0x1ff;
			_EK[i + 24] = _EK[i + 8] >> 9;
		}
	}

	private int encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) {
		int result[] = new int[2];
		int D0 = BytesTo32bits(src, srcIndex);
		int D1 = BytesTo32bits(src, srcIndex + 4);
		MISTY1_Encipher(D0, D1, result);
		Bits32ToBytes(result[0], dst, dstIndex);
		Bits32ToBytes(result[1], dst, dstIndex + 4);
		return BLOCK_SIZE;
	}

	private int decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) {
		int result[] = new int[2];
		int D0 = BytesTo32bits(src, srcIndex + 4);
		int D1 = BytesTo32bits(src, srcIndex);
		MISTY1_Decipher(D0, D1, result);
		Bits32ToBytes(result[0], dst, dstIndex);
		Bits32ToBytes(result[1], dst, dstIndex + 4);
		return BLOCK_SIZE;
	}

	protected final void MISTY1_Encipher(int D0, int D1, int result[]) {
		int Li = D0, Ri = D1;
		Li = FL(Li, 0);
		Ri = FL(Ri, 1);
		Ri = Ri ^ FO(Li, 0);
		Li = Li ^ FO(Ri, 1);
		Li = FL(Li, 2);
		Ri = FL(Ri, 3);
		Ri = Ri ^ FO(Li, 2);
		Li = Li ^ FO(Ri, 3);
		Li = FL(Li, 4);
		Ri = FL(Ri, 5);
		Ri = Ri ^ FO(Li, 4);
		Li = Li ^ FO(Ri, 5);
		Li = FL(Li, 6);
		Ri = FL(Ri, 7);
		Ri = Ri ^ FO(Li, 6);
		Li = Li ^ FO(Ri, 7);
		Li = FL(Li, 8);
		Ri = FL(Ri, 9);
		result[0] = Ri;
		result[1] = Li;
	}

	protected final void MISTY1_Decipher(int D0, int D1, int result[]) {
		int Li = D0, Ri = D1;
		Li = FLINV(Li, 8);
		Ri = FLINV(Ri, 9);
		Li = Li ^ FO(Ri, 7);
		Ri = Ri ^ FO(Li, 6);
		Li = FLINV(Li, 6);
		Ri = FLINV(Ri, 7);
		Li = Li ^ FO(Ri, 5);
		Ri = Ri ^ FO(Li, 4);
		Li = FLINV(Li, 4);
		Ri = FLINV(Ri, 5);
		Li = Li ^ FO(Ri, 3);
		Ri = Ri ^ FO(Li, 2);
		Li = FLINV(Li, 2);
		Ri = FLINV(Ri, 3);
		Li = Li ^ FO(Ri, 1);
		Ri = Ri ^ FO(Li, 0);
		Li = FLINV(Li, 0);
		Ri = FLINV(Ri, 1);
		result[0] = Li;
		result[1] = Ri;
	}

	private int FO(int FO_IN, int k) {
		int t0, t1;
		t0 = (FO_IN >> 16) & 0xffff;
		t1 = FO_IN & 0xffff;
		t0 = t0 ^ _EK[k];
		t0 = FI(t0, _EK[(k + 5) % 8 + 8]);
		t0 = t0 ^ t1;
		t1 = t1 ^ _EK[(k + 2) % 8];
		t1 = FI(t1, _EK[(k + 1) % 8 + 8]);
		t1 = t1 ^ t0;
		t0 = t0 ^ _EK[(k + 7) % 8];
		t0 = FI(t0, _EK[(k + 3) % 8 + 8]);
		t0 = t0 ^ t1;
		t1 = t1 ^ _EK[(k + 4) % 8];
		return (t1 << 16) | t0;
	}

	private int FI(int FI_IN, int FI_KEY) {
		int d9, d7;
		d9 = (FI_IN >> 7) & 0x1ff;
		d7 = FI_IN & 0x7f;
		d9 = S9TABLE[d9] ^ d7;
		d7 = S7TABLE[d7] ^ d9;
		d7 = d7 & 0x7f;
		d7 = d7 ^ ((FI_KEY >> 9) & 0x7f);
		d9 = d9 ^ (FI_KEY & 0x1ff);
		d9 = S9TABLE[d9] ^ d7;
		return (d7 << 9) | d9;
	}

	private int FL(int FL_IN, int k) {
		int d0, d1;
		d0 = (FL_IN >> 16) & 0xffff;
		d1 = FL_IN & 0xffff;
		if ((k & 1) == 0) {
			d1 = d1 ^ (d0 & _EK[k / 2]);
			d0 = d0 ^ (d1 | _EK[(k / 2 + 6) % 8 + 8]);
		} else {
			d1 = d1 ^ (d0 & _EK[((k - 1) / 2 + 2) % 8 + 8]);
			d0 = d0 ^ (d1 | _EK[((k - 1) / 2 + 4) % 8]);
		}
		return (d0 << 16) | d1;
	}

	private int FLINV(int FL_IN, int k) {
		int d0, d1;
		d0 = (FL_IN >> 16) & 0xffff;
		d1 = FL_IN & 0xffff;
		if ((k & 1) == 0) {
			d0 = d0 ^ (d1 | _EK[(k / 2 + 6) % 8 + 8]);
			d1 = d1 ^ (d0 & _EK[k / 2]);
		} else {
			d0 = d0 ^ (d1 | _EK[((k - 1) / 2 + 4) % 8]);
			d1 = d1 ^ (d0 & _EK[((k - 1) / 2 + 2) % 8 + 8]);
		}
		return (d0 << 16) | d1;
	}

	protected final void Bits32ToInts(int in, int[] b, int offset) {
		b[offset + 3] = (in & 0xff);
		b[offset + 2] = ((in >>> 8) & 0xff);
		b[offset + 1] = ((in >>> 16) & 0xff);
		b[offset] = ((in >>> 24) & 0xff);
	}

	protected final int IntsTo32bits(int[] b, int i) {
		int rv = 0;

		rv = ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16)
				| ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff));

		return rv;
	}

	protected final void Bits32ToBytes(int in, byte[] b, int offset) {
		b[offset + 3] = (byte) in;
		b[offset + 2] = (byte) (in >>> 8);
		b[offset + 1] = (byte) (in >>> 16);
		b[offset] = (byte) (in >>> 24);
	}

	protected final int BytesTo32bits(byte[] b, int i) {
		return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16)
				| ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff));
	}

	private final static int[] S7TABLE = { 0x1b, 0x32, 0x33, 0x5a, 0x3b, 0x10,
			0x17, 0x54, 0x5b, 0x1a, 0x72, 0x73, 0x6b, 0x2c, 0x66, 0x49, 0x1f,
			0x24, 0x13, 0x6c, 0x37, 0x2e, 0x3f, 0x4a, 0x5d, 0x0f, 0x40, 0x56,
			0x25, 0x51, 0x1c, 0x04, 0x0b, 0x46, 0x20, 0x0d, 0x7b, 0x35, 0x44,
			0x42, 0x2b, 0x1e, 0x41, 0x14, 0x4b, 0x79, 0x15, 0x6f, 0x0e, 0x55,
			0x09, 0x36, 0x74, 0x0c, 0x67, 0x53, 0x28, 0x0a, 0x7e, 0x38, 0x02,
			0x07, 0x60, 0x29, 0x19, 0x12, 0x65, 0x2f, 0x30, 0x39, 0x08, 0x68,
			0x5f, 0x78, 0x2a, 0x4c, 0x64, 0x45, 0x75, 0x3d, 0x59, 0x48, 0x03,
			0x57, 0x7c, 0x4f, 0x62, 0x3c, 0x1d, 0x21, 0x5e, 0x27, 0x6a, 0x70,
			0x4d, 0x3a, 0x01, 0x6d, 0x6e, 0x63, 0x18, 0x77, 0x23, 0x05, 0x26,
			0x76, 0x00, 0x31, 0x2d, 0x7a, 0x7f, 0x61, 0x50, 0x22, 0x11, 0x06,
			0x47, 0x16, 0x52, 0x4e, 0x71, 0x3e, 0x69, 0x43, 0x34, 0x5c, 0x58,
			0x7d }, S9TABLE = { 0x1c3, 0x0cb, 0x153, 0x19f, 0x1e3, 0x0e9,
			0x0fb, 0x035, 0x181, 0x0b9, 0x117, 0x1eb, 0x133, 0x009, 0x02d,
			0x0d3, 0x0c7, 0x14a, 0x037, 0x07e, 0x0eb, 0x164, 0x193, 0x1d8,
			0x0a3, 0x11e, 0x055, 0x02c, 0x01d, 0x1a2, 0x163, 0x118, 0x14b,
			0x152, 0x1d2, 0x00f, 0x02b, 0x030, 0x13a, 0x0e5, 0x111, 0x138,
			0x18e, 0x063, 0x0e3, 0x0c8, 0x1f4, 0x01b, 0x001, 0x09d, 0x0f8,
			0x1a0, 0x16d, 0x1f3, 0x01c, 0x146, 0x07d, 0x0d1, 0x082, 0x1ea,
			0x183, 0x12d, 0x0f4, 0x19e, 0x1d3, 0x0dd, 0x1e2, 0x128, 0x1e0,
			0x0ec, 0x059, 0x091, 0x011, 0x12f, 0x026, 0x0dc, 0x0b0, 0x18c,
			0x10f, 0x1f7, 0x0e7, 0x16c, 0x0b6, 0x0f9, 0x0d8, 0x151, 0x101,
			0x14c, 0x103, 0x0b8, 0x154, 0x12b, 0x1ae, 0x017, 0x071, 0x00c,
			0x047, 0x058, 0x07f, 0x1a4, 0x134, 0x129, 0x084, 0x15d, 0x19d,
			0x1b2, 0x1a3, 0x048, 0x07c, 0x051, 0x1ca, 0x023, 0x13d, 0x1a7,
			0x165, 0x03b, 0x042, 0x0da, 0x192, 0x0ce, 0x0c1, 0x06b, 0x09f,
			0x1f1, 0x12c, 0x184, 0x0fa, 0x196, 0x1e1, 0x169, 0x17d, 0x031,
			0x180, 0x10a, 0x094, 0x1da, 0x186, 0x13e, 0x11c, 0x060, 0x175,
			0x1cf, 0x067, 0x119, 0x065, 0x068, 0x099, 0x150, 0x008, 0x007,
			0x17c, 0x0b7, 0x024, 0x019, 0x0de, 0x127, 0x0db, 0x0e4, 0x1a9,
			0x052, 0x109, 0x090, 0x19c, 0x1c1, 0x028, 0x1b3, 0x135, 0x16a,
			0x176, 0x0df, 0x1e5, 0x188, 0x0c5, 0x16e, 0x1de, 0x1b1, 0x0c3,
			0x1df, 0x036, 0x0ee, 0x1ee, 0x0f0, 0x093, 0x049, 0x09a, 0x1b6,
			0x069, 0x081, 0x125, 0x00b, 0x05e, 0x0b4, 0x149, 0x1c7, 0x174,
			0x03e, 0x13b, 0x1b7, 0x08e, 0x1c6, 0x0ae, 0x010, 0x095, 0x1ef,
			0x04e, 0x0f2, 0x1fd, 0x085, 0x0fd, 0x0f6, 0x0a0, 0x16f, 0x083,
			0x08a, 0x156, 0x09b, 0x13c, 0x107, 0x167, 0x098, 0x1d0, 0x1e9,
			0x003, 0x1fe, 0x0bd, 0x122, 0x089, 0x0d2, 0x18f, 0x012, 0x033,
			0x06a, 0x142, 0x0ed, 0x170, 0x11b, 0x0e2, 0x14f, 0x158, 0x131,
			0x147, 0x05d, 0x113, 0x1cd, 0x079, 0x161, 0x1a5, 0x179, 0x09e,
			0x1b4, 0x0cc, 0x022, 0x132, 0x01a, 0x0e8, 0x004, 0x187, 0x1ed,
			0x197, 0x039, 0x1bf, 0x1d7, 0x027, 0x18b, 0x0c6, 0x09c, 0x0d0,
			0x14e, 0x06c, 0x034, 0x1f2, 0x06e, 0x0ca, 0x025, 0x0ba, 0x191,
			0x0fe, 0x013, 0x106, 0x02f, 0x1ad, 0x172, 0x1db, 0x0c0, 0x10b,
			0x1d6, 0x0f5, 0x1ec, 0x10d, 0x076, 0x114, 0x1ab, 0x075, 0x10c,
			0x1e4, 0x159, 0x054, 0x11f, 0x04b, 0x0c4, 0x1be, 0x0f7, 0x029,
			0x0a4, 0x00e, 0x1f0, 0x077, 0x04d, 0x17a, 0x086, 0x08b, 0x0b3,
			0x171, 0x0bf, 0x10e, 0x104, 0x097, 0x15b, 0x160, 0x168, 0x0d7,
			0x0bb, 0x066, 0x1ce, 0x0fc, 0x092, 0x1c5, 0x06f, 0x016, 0x04a,
			0x0a1, 0x139, 0x0af, 0x0f1, 0x190, 0x00a, 0x1aa, 0x143, 0x17b,
			0x056, 0x18d, 0x166, 0x0d4, 0x1fb, 0x14d, 0x194, 0x19a, 0x087,
			0x1f8, 0x123, 0x0a7, 0x1b8, 0x141, 0x03c, 0x1f9, 0x140, 0x02a,
			0x155, 0x11a, 0x1a1, 0x198, 0x0d5, 0x126, 0x1af, 0x061, 0x12e,
			0x157, 0x1dc, 0x072, 0x18a, 0x0aa, 0x096, 0x115, 0x0ef, 0x045,
			0x07b, 0x08d, 0x145, 0x053, 0x05f, 0x178, 0x0b2, 0x02e, 0x020,
			0x1d5, 0x03f, 0x1c9, 0x1e7, 0x1ac, 0x044, 0x038, 0x014, 0x0b1,
			0x16b, 0x0ab, 0x0b5, 0x05a, 0x182, 0x1c8, 0x1d4, 0x018, 0x177,
			0x064, 0x0cf, 0x06d, 0x100, 0x199, 0x130, 0x15a, 0x005, 0x120,
			0x1bb, 0x1bd, 0x0e0, 0x04f, 0x0d6, 0x13f, 0x1c4, 0x12a, 0x015,
			0x006, 0x0ff, 0x19b, 0x0a6, 0x043, 0x088, 0x050, 0x15f, 0x1e8,
			0x121, 0x073, 0x17e, 0x0bc, 0x0c2, 0x0c9, 0x173, 0x189, 0x1f5,
			0x074, 0x1cc, 0x1e6, 0x1a8, 0x195, 0x01f, 0x041, 0x00d, 0x1ba,
			0x032, 0x03d, 0x1d1, 0x080, 0x0a8, 0x057, 0x1b9, 0x162, 0x148,
			0x0d9, 0x105, 0x062, 0x07a, 0x021, 0x1ff, 0x112, 0x108, 0x1c0,
			0x0a9, 0x11d, 0x1b0, 0x1a6, 0x0cd, 0x0f3, 0x05c, 0x102, 0x05b,
			0x1d9, 0x144, 0x1f6, 0x0ad, 0x0a5, 0x03a, 0x1cb, 0x136, 0x17f,
			0x046, 0x0e1, 0x01e, 0x1dd, 0x0e6, 0x137, 0x1fa, 0x185, 0x08c,
			0x08f, 0x040, 0x1b5, 0x0be, 0x078, 0x000, 0x0ac, 0x110, 0x15e,
			0x124, 0x002, 0x1bc, 0x0a2, 0x0ea, 0x070, 0x1fc, 0x116, 0x15c,
			0x04c, 0x1c2 };
}
